package com.jet.chat.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

import com.jet.chat.command.ChatCommand;
import com.jet.chat.event.Action;
import com.jet.chat.event.ActionListener;
import com.jet.chat.exception.ChatExceptionDBService;
import com.jet.chat.message.MessageReaderBufferedReader;
import com.jet.chat.message.MessageWriterPrintWriter;
import com.jet.chat.service.DBConnectionService;
import com.jet.chat.thread.ChatThread;


public class ChatServer {
	private int port;
	private String dbname;
	private DBConnectionService dbConnectionService;
	private HashMap<String, ChatChannel> channels = new HashMap<String, ChatChannel>();
	private HashMap<String, ChatCommand> chatCommands = new HashMap<String, ChatCommand>();
	private HashMap<String, String> chatCommandAliases = new HashMap<String, String>();
	private String help;
	private static final ClassLoader classLoader = ChatServer.class.getClassLoader();
	
	private HashMap<String, ArrayList<ActionListener>> actionListeners = new HashMap<String, ArrayList<ActionListener>>();
	
	public void addActionListener(String eventName, ActionListener listener) {
		ArrayList<ActionListener> listeners = actionListeners.get(eventName);
		if (listeners == null) {
			listeners = new ArrayList<ActionListener>();
			this.actionListeners.put(eventName, listeners);
		}
		listeners.add(listener);
	}
	//TODO:[au] для чего выдавать ссылку на пустой массив - если ссылку не храним в ActionListener?
	// может быть обрабатывать null тем объектом кто запрашивает getActionListener? - просто какая-то левая responsibility
	public ArrayList<ActionListener> getActionListeners(String actionName) {
		ArrayList<ActionListener> result;
		result = this.actionListeners.get(actionName);
		if (result == null) {
			result = new ArrayList<ActionListener>();
		}
		return result;
	}
	
	public void addChatCommandAlias(String alias, String command) {
		chatCommandAliases.put(alias, command);
	}
	
	public HashMap<String, String> getChatCommandAliases() {
		return chatCommandAliases;
	}
	
	public HashMap<String, ChatCommand> getChatCommands() {
		return chatCommands;
	}
	
	public synchronized ChatCommand addChatCommand(String commandName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		//[DF] FIXME: Concurrent access to ChatCommands detected
		// at least if global lock will be removed
		//All class loads probably better to be done during file load,
		// or synchronization have to be done.
		Class<?> commandClass = classLoader.loadClass("com.jet.chat.command.ChatCommand" + commandName);
		ChatCommand chatCommand = (ChatCommand) commandClass.newInstance();
		//[ek] XXX cool )) applications server invented ))

		chatCommands.put(commandName, chatCommand);
		
		return chatCommand;
	}

	public void addChannel(ChatChannel chatChannel) {
		channels.put(chatChannel.getChannelName(), chatChannel);
	}
	
	public ChatChannel getChannel(String channelName) {
		return channels.get(channelName);
	}
	
	public HashMap<String, ChatChannel> getChannels() {
		return channels;
	}
	
	public String getHelp() {
		return help;
	}
	
	public ChatServer(int port, String dbname) {
		this.port = port;
		this.dbname = dbname;
		this.dbConnectionService = new DBConnectionService(dbname);
		
		addActionListener("onLogin", new ActionListener() {
			public void actionPerformed(Action event) {
				System.out.println(event.getGreetings());
			}
		});
		
		this.help = getFileContent("help.txt");
	}
	
	private void execute() {
		
		loadCommandsFromFile("commands.txt");

		try {
			ServerSocket serverSocket = new ServerSocket(port);
			System.out.println("server started");
			dbConnectionService.createConnection();
			
			new Thread(new ChatThread(new MessageReaderBufferedReader(new BufferedReader(new InputStreamReader(System.in))), new MessageWriterPrintWriter(new PrintWriter(System.out)), "server console shutdown")).start();
			
			ChatChannel chatChannel = new ChatChannel("Default");
			addChannel(chatChannel);
			
			Socket socket = null;
			Thread session = null;
			while (true) {
				socket = serverSocket.accept();
				System.out.println("socket accepted");
				session = new Thread(new ChatSession(this, "Default", socket));
				session.start();
				System.out.println("session started");
			}
			
		} catch (IOException e) {
			System.out.println("port not is not available");
			e.printStackTrace();
		} catch (ChatExceptionDBService e) {
			e.printStackTrace();
		} finally {
			dbConnectionService.shutdown();
		}

	}
	private void loadCommandsFromFile(String fileName) {
		
		File cfgFile = new File(fileName);
		try {
				LineNumberReader cfgReader;
				cfgReader = new LineNumberReader(new FileReader(cfgFile.getAbsolutePath()));
				String line;
				String alias;
				String command;
			try {
				while (((line = cfgReader.readLine()) != null)) {
					alias = line.split("\\s")[0];
					command = line.split("\\s")[1];
					addChatCommandAlias(alias, command);
				}
			} catch (IOException e1) {
				System.out.println("config file can not be read. chat commands will disabled");
			}
		} catch (FileNotFoundException e1) {
			System.out.println("config file not found. chat commands will disabled");
		}
	}
	
	private String getFileContent(String filename) {
		File file = new File(filename);
		int ch;
		StringBuffer content = new StringBuffer("");
		FileInputStream fin = null;
		try {
			fin = new FileInputStream(file);
			while ((ch = fin.read()) != -1)
			content.append((char) ch);
			fin.close();
		} catch (Exception e) {
			System.out.println(e);
		}
		return content.toString();
	}
	
	public static void main(String[] arg) {
		ChatServer chatServer = new ChatServer(30030, "chatdb");
		chatServer.execute();
	}

	public String getDbname() {
		return dbname;
	}

	public DBConnectionService getDBConnectionService() {
		return dbConnectionService;
	}

}
